
package physiqueEngine;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Timer;
import java.util.TimerTask;

import javax.swing.AbstractAction;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;

import physiqueEngine.formes.Oval;
import physiqueEngine.formes.PhysBody;
import physiqueEngine.formes.Physic;
import physiqueEngine.formes.Rectangle;
import physiqueEngine.graphs.Graph;
import physiqueEngine.thread.TimeManager;

/**
 * @author Jean-Simon Garneau
 * @author David Bernard
 * 
 * @since Java 1.6
 * 
 *        Cette classe représente la scène et dessine la scène.
 */
public class WorldPanel extends JPanel implements KeyListener, MouseListener, Refreshable,
        TimeManager
{
    // Constantes.
    private static final int DEF_WIDTH = 1000;
    private static final int DEF_HEIGTH = 1000;
    private static final double DEF_GRAVITY = 5d;
    private static final double DEF_GRAVITY_DIR = 270d;
    private static final double DEF_AIR_FRICTION = 0.02d;
    private static final boolean DEF_SPEED_LINES = true;
    private static final int DEF_PERIOD = 16;
    private static final int DEF_GRID_GAP = 25;
    private static final Color DEF_BACKGROUND_COLOR = Color.WHITE;
    private static final Color DEF_GRID_COLOR = Color.BLUE;
    private static final Color DEF_FOCUS_COLOR = Color.RED;

    // TODO encapsuler ou rendre final.
    public double gravity = 0;
    public double gravDir = 0;
    public double airFriction = 0d;
    
    //Attributs graphiques
    protected Graph speedGraph = null;

    // Attributs
    private long tick = 0;
    private long period = 0;
    private boolean paintSpeedLines = false;
    private Timer timeManager = null;
    private JPopupMenu test1 = null;
    private AbstractAction addBody = null;
    private PhysBody focus = null;
    private Color focusColor = null;

    private ArrayList<PhysBody> listBody = new ArrayList<PhysBody>();

    /**
     * Constructeur par défaut.
     */
    public WorldPanel()
    {
        this(WorldPanel.DEF_WIDTH, WorldPanel.DEF_HEIGTH);
    }

    /**
     * Constructeur principal.
     * 
     * @param width La largeur du monde.
     * @param height La Hauteur du monde.
     */
    public WorldPanel(int width, int height)
    {
        this.initActions();

        // Configurations du JPanel
        this.setPreferredSize(new Dimension(width, height));
        this.setSize(width, height);
        this.setBackground(WorldPanel.DEF_BACKGROUND_COLOR);
        this.addMouseListener(this);

        // TODO Faire le set/get
        // Setter le paramètres de l'environnement par défauts.
        this.setPeriod(WorldPanel.DEF_PERIOD);
        this.gravity = WorldPanel.DEF_GRAVITY;
        this.gravDir = WorldPanel.DEF_GRAVITY_DIR;
        this.airFriction = WorldPanel.DEF_AIR_FRICTION;
        this.paintSpeedLines = WorldPanel.DEF_SPEED_LINES;
        this.setFocusColor(WorldPanel.DEF_FOCUS_COLOR);

        // Pour test
        this.test1 = new JPopupMenu();
        test1.add(this.addBody);
        this.add(this.test1);

    }

    /**
     * Initialiser les actions.
     */
    private void initActions()
    {
        addBody = new AbstractAction("Add Body")
        {
            @Override
            public void actionPerformed(ActionEvent arg0)
            {
                
            }
        };
    }

    /**
     * Dessine le panneau.
     */
    @Override
    public void paintComponent(Graphics g)
    {
        // Caster l'objet graphique en objet graphique2D.
        Graphics2D g2d = (Graphics2D) g;

        // Appeler la méthode paint component de JPanel.
        super.paintComponent(g);

        // Dessiner la grille.
        g.setColor(WorldPanel.DEF_GRID_COLOR);
        this.paintGrid(g, DEF_GRID_GAP);

        // TODO utiliser un itérator pour avoir plus de rendement.
        // Dessiner les bodies
        for (PhysBody body : this.listBody)
        {
            g2d.setColor(body.getColor());
            g2d.fill(body);
        }

        // Dessiner les lignes de vitesse.
        g2d.setColor(Color.RED);
        if (this.paintSpeedLines)
        {
            for (PhysBody body : this.listBody)
            {
                g2d.draw(body.getSpeedLine());
            }
        }

        // Dessiner le cadre du focus.
        if (this.focus != null)
        {
            g2d.setColor(this.focusColor);
            g2d.draw(this.focus.getBounds2D());
            
            //Acces au vecteur
            double vecteur = this.focus.getVecteur();
            System.out.println(vecteur);
            
        }
    }

    /**
     * Dessiner la grille.
     * 
     * @param g un object de type Graphique.
     * @param gapSize
     */
    private void paintGrid(Graphics g, int gapSize)
    {
        int height = 0;
        int width = 0;
        Dimension dim = this.getSize();

        height = dim.height + gapSize;
        width = dim.width + gapSize;

        // Dessiner les lignes verticales.
        for (int i = 0; i < width; i += gapSize)
        {
            g.drawString("" + i, i, 10);
            g.drawLine(i, 0, i, height);
        }

        // Dessiner les lignes horizontales.
        for (int i = 0; i < height; i += gapSize)
        {
            g.drawString("" + i, 0, i);
            g.drawLine(0, i, width, i);
        }
    }

    /**
     * Sert à ajouter un body dans la scène.
     * 
     * @param body Le body à ajouter.
     */
    private void addBody(PhysBody body)
    {
        this.listBody.add(body);
    }

    /**
     * Retourne le nombre de body dans la scene
     * 
     * @return int, Le nombre de body dans la scène.
     */
    public int getBodyCount()
    {
        return this.listBody.size();
    }

    /**
     * Crée et ajoute une Oval.
     * 
     * @return Oval, Un pointeur vers l'oval ajouté.
     */
    public Oval addOval()
    {
        Oval newBody = new Oval(this);

        this.addBody(newBody);

        return newBody;
    }

    /**
     * Crée et ajoute un Rectangle.
     * 
     * @return Rectangle, un pointeur vers le rectangle.
     */
    public Rectangle addRectangle()
    {
        Rectangle newBody = new Rectangle(this);

        this.addBody(newBody);

        return newBody;
    }

    @Override
    public void refresh()
    {
        WorldPanel.this.tick++;
        Physic.collision(this.listBody);
        for (PhysBody body : this.listBody)
        {
            body.refresh();
        }
    }

    @Override
    public boolean setPeriod(long period)
    {
        boolean ok = true;

        if (ok)
        {
            this.period = period;
        }

        return ok;
    }

    @Override
    public long getPeriod()
    {
        return this.period;
    }

    @Override
    public void keyPressed(KeyEvent arg0)
    {
    }

    @Override
    public void keyReleased(KeyEvent arg0)
    {
    }

    @Override
    public void keyTyped(KeyEvent arg0)
    {
    }

    @Override
    public void mouseEntered(MouseEvent arg0)
    {
    }

    @Override
    public void mouseExited(MouseEvent arg0)
    {
    }

    @Override
    public void mouseClicked(MouseEvent e)
    {
        if (e.getButton() == MouseEvent.BUTTON3)
        {
            test1.show(e.getComponent(), e.getX(), e.getY());
        }
        else if (e.getButton() == MouseEvent.BUTTON1)
        {
            this.setFocus(e.getPoint());
        }
    }

    @Override
    public void mousePressed(MouseEvent e)
    {
    }

    @Override
    public void mouseReleased(MouseEvent arg0)
    {
    }

    @Override
    public void setTicking(boolean ticking)
    {
        if (ticking)
        {
            this.timeManager = new Timer();
            this.timeManager.scheduleAtFixedRate(new TickAction(), 0, this.getPeriod());
        }
        else
        {
            this.timeManager.cancel();
            this.timeManager = null;
        }
    }

    @Override
    public boolean isTicking()
    {
        return (this.timeManager != null);
    }
    
    public long getTickCount()
    {
        return this.tick;
    }

    public void purgeShapes()
    {
        this.listBody = new ArrayList<PhysBody>();
        this.focus = null;
    }

    // TODO à amiliorer
    private void setFocus(Point2D p2d)
    {
        PhysBody newFocus = null;

        for (PhysBody body : this.listBody)
        {
            if (body.contains(p2d))
            {
                newFocus = body;
            }
        }

        this.focus = newFocus;
    }

    public PhysBody getFocus()
    {
        return this.focus;
    }

    public void setFocusColor(Color focusColor)
    {
        this.focusColor = focusColor;
    }

    class TickAction extends TimerTask
    {
        @Override
        public void run()
        {
            WorldPanel.this.refresh();
        }
    }
}
